﻿package com.reyco1.audio
{
	/*
	imports
	*/
	import com.reyco1.extension.DispatchExtension;
	import com.reyco1.events.SoundEvent;
	import com.reyco1.util.IntervalUtil;
    import com.reyco1.util.MathUtil;
    import com.reyco1.audio.AudioCuePoint;
    import com.reyco1.events.AudioCuePointEvent;
    
    import flash.events.ProgressEvent;
	import flash.events.IOErrorEvent;
	import flash.events.Event;
    import flash.media.Sound;
    import flash.media.SoundChannel;
    import flash.net.URLRequest;
    import flash.utils.Dictionary;
    import flash.media.SoundTransform;
	/*
	class
	*/
	public class SoundManager extends DispatchExtension
	{
		/*
		variables
		*/
		private var soundDictionary:Dictionary;
		private var externalSounds:Array;
		private var librarySounds:Array;
		private var loadIndex:Number;
		private var totalFilesToLoad:Number;
		
		/**
		 * 
		 * 
		 */		
		public function SoundManager():void
		{
			this.soundDictionary = new Dictionary(true);
			this.externalSounds = new Array();
			this.librarySounds = new Array();
			this.loadIndex = 0;
		}
		
		/**
		 * Adds a sound to the loading queue.
		 * @param $tag : String - a name to identify your sound by
		 * @param $path : * - the path to the sound file
		 * 
		 */		
		public function addToQueue($tag:String, $path:String):void
		{
			var sndChannel:SoundChannel = new SoundChannel();
			var sndObject:Object = new Object();
			var sndTemp:Sound = new Sound();
			var sndRequest:URLRequest = new URLRequest($path);
			var sndPositionTimer:IntervalUtil = new IntervalUtil($tag+"_position_interval", positionInterval, $tag);
			sndPositionTimer.outputSilenced = true;
			var sndCuePoints:AudioCuePoint = new AudioCuePoint();
			sndCuePoints.addEventListener(AudioCuePointEvent.CUE_POINT, cuePointHandler);
						
			sndObject.request = sndRequest;
			sndObject.cuePoints = sndCuePoints;
			sndObject.sound = sndTemp;
			sndObject.channel = sndChannel;
			sndObject.paused = true;
			sndObject.position = 0;
			sndObject.name = $tag;
			sndObject.positionTimer = sndPositionTimer;
			
			this.soundDictionary[$tag] = sndObject;
			this.externalSounds.push(sndObject);
		}
		
		/**
		 * Adds a sound to the SoundManager instance form the library
		 * @param $tag : String - a name to identify your sound by
		 * @param $libraryItem : * - the class name of the sound in the library
		 * 
		 */		
		public function addFromLib($tag:String, $libraryItem:*):void
		{
			var sndChannel:SoundChannel = new SoundChannel();
			var sndObject:Object = new Object();
			var sndTemp:Sound = new $libraryItem;
			var sndPositionTimer:IntervalUtil = new IntervalUtil($tag+"_position", positionInterval, $tag);
			sndPositionTimer.outputSilenced = true;
			var sndCuePoints:AudioCuePoint = new AudioCuePoint();
			sndCuePoints.addEventListener(AudioCuePointEvent.CUE_POINT, cuePointHandler);
						
			sndObject.sound = sndTemp;
			sndObject.cuePoints = sndCuePoints;
			sndObject.channel = sndChannel;
			sndObject.paused = true;
			sndObject.position = 0;
			sndObject.name = $tag;
			sndObject.positionTimer = sndPositionTimer;
			
			this.soundDictionary[$tag] = sndObject;
			this.librarySounds.push(sndObject);
		}
		
		/**
		 * Initiates the loading of the sounds in the loading queue
		 * 
		 */		
		public function loadSounds($index:Number = 0):void
		{
			this.totalFilesToLoad = this.externalSounds.length;
			this.initiateLoad($index);
		}
		
		/**
		 * Plays a sound
		 * @param $tag : the song id
		 * @param $params : 3 optional paramaters:
		 * 
		 * <p>
		 * <ul>
		 * <li><b>volume</b> : a number between 1 to 100</li>
		 * <li><b>offset</b> : number in seconds from which to start the sound</li>
		 * <li><b>loops</b> : number of times the sound should loop</li>
		 * </ul>
		 * </p>
		 * 
		 */		
		public function play($tag:String, $params:Object = null):SoundChannel
		{
			var volume:Number = ($params == null) ? 1 : $params.volume;
			var loops:Number = ($params == null) ? 0 : $params.loops;
			var offset:Number = ($params == null) ? this.soundDictionary[$tag].position : $params.offset * 1000;
			
			if(this.soundDictionary[$tag].paused){
				this.soundDictionary[$tag].paused = false;
			}
			
			this.soundDictionary[$tag].channel = this.soundDictionary[$tag].sound.play(offset, loops);
			this.setVolume($tag, volume);
			this.soundDictionary[$tag].positionTimer.start({delay:.01});
			return this.soundDictionary[$tag].channel
		}
		
		/**
		 * Stops a sound from playing
		 * @param $tag : String - id of the sound to stop playing
		 * 
		 */		
		public function stop($tag:String):void
		{
			this.soundDictionary[$tag].channel.stop();
			this.soundDictionary[$tag].paused = true;
			this.soundDictionary[$tag].position = 0;
			this.soundDictionary[$tag].positionTimer.stop();
		}
		
		/**
		 * Pauses a sound
		 * @param $tag : String - id name of the sound to pause
		 * 
		 */		
		public function pause($tag:String):void
		{
			var pausePosition:Number = this.soundDictionary[$tag].channel.position;
			this.soundDictionary[$tag].position = pausePosition;
			this.soundDictionary[$tag].paused = true;
			this.soundDictionary[$tag].positionTimer.stop();
			this.soundDictionary[$tag].channel.stop();
		}
		
		/**
		 * Stops all playing sounds
		 * 
		 */		
		public function stopAll():void
		{
			var allSounds:Array = this.externalSounds.concat(this.librarySounds);
			
			for(var a:Number = 0; a<allSounds.length; a++){
				this.stop(soundDictionary[allSounds[a]]);
			}
		}
		
		/**
		 * Adds a cuepoint to the selected sound
		 * @param $tag : name of the sound
		 * @param $cuePoint : an object with two mandatory values:
		 * 
		 * <ul>
		 * <li><b>time</b> : Number - time in seconds of where to place the cure point</li>
		 * <li><b>name</b> : String - a unique name for the cue point being added</li>
		 * </ul>
		 * 
		 */		
		public function addCuePoint($tag:String, $cuePoint:Object):void
		{
			var tempObj:Object = new Object();
			tempObj = $cuePoint;
			tempObj.owner = $tag;
			this.soundDictionary[$tag].cuePoints.addCuePoint(tempObj);
		}
		
		/**
		 * Removes a cue point added using the addCuePoint method.
		 * @param $tag : name of the sound
		 * @param $cuePointName : String - naem of the cue point to be removed
		 * 
		 */	
		public function removeCuePoint($tag:String, $cuePointName:String):void
		{
			this.soundDictionary[$tag].cuePoints.removeCuePoint($cuePointName);
		}
		
		/**
		 * Once a cuepoint has been fired, it needs to be reset in order to be listened to again. This method
		 * resets all the cue ponts for that prupose.
		 * 
		 *  @param $tag : name of the sound
		 * 
		 */		
		public function resetCuePoints($tag:String):void
		{
			this.soundDictionary[$tag].cuePoints.reset();
		}
				
		private function initiateLoad($sndIndex:Number):void
		{
			var sound:Sound = this.externalSounds[$sndIndex].sound;
			var request:URLRequest = this.externalSounds[$sndIndex].request;
			
			sound.addEventListener(Event.COMPLETE, loadCompleteHandler);
            sound.addEventListener(Event.ID3, id3Handler);
            sound.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            sound.addEventListener(ProgressEvent.PROGRESS, progressHandler);
            
            sound.load(request);
		}
		
		/*
		
		EVENT HANDLERS
		
		*/				
		
		private function loadCompleteHandler(event:Event):void 
		{
			this.dispatchEvent(new SoundEvent(SoundEvent.SOUND_LOADED, {name:this.externalSounds[this.loadIndex].name}));
            this.loadIndex++;
            
            if(this.externalSounds[this.loadIndex] != undefined){
            	this.loadSounds(this.loadIndex);
            }else{
            	this.dispatchEvent(new SoundEvent(SoundEvent.ALL_SOUNDS_LOADED, {}));
            }
        }

        private function id3Handler($event:Event):void 
		{
            this.dispatchEvent(new SoundEvent(SoundEvent.ID3, {id3:$event}));
        }

        private function ioErrorHandler($event:Event):void 
		{
            this.dispatchEvent(new SoundEvent(SoundEvent.IOError, {ioError:$event}));      
        }

        private function progressHandler($event:ProgressEvent):void 
		{
			var loadPercentage:Number = MathUtil.percentage($event.bytesLoaded, $event.bytesTotal);
			var totalSoundsToLoad:Number = this.totalFilesToLoad;
			var curretLoadingIndex:Number = this.loadIndex +1;
            this.dispatchEvent(new SoundEvent(SoundEvent.LOAD_PROGRESS, {percent:loadPercentage, totalFiles:totalSoundsToLoad, currentFileIndex:curretLoadingIndex}));
        }
        
        private function cuePointHandler($event:AudioCuePointEvent):void
        {
        	this.dispatchEvent(new SoundEvent(SoundEvent.CUE_POINT, $event.params));
        }
        
        private function positionInterval($tag:String):void
        {
        	var soundName:String = $tag
        	var soundPosition:Number = Math.round(this.soundDictionary[soundName].channel.position * .001);
        	var soundDuration:Number = Math.round(this.soundDictionary[soundName].sound.length * .001);
        	var positionPercentage:Number = MathUtil.percentage(soundPosition, soundDuration);
        	
        	if(this.soundDictionary[$tag].cuePoints.hasCuePoints){
        		this.soundDictionary[$tag].cuePoints.listen(this.soundDictionary[$tag].channel);
        	}
        	
        	this.dispatchEvent(new SoundEvent(SoundEvent.SOUND_PROGRESS, {name:soundName, position:soundPosition, duration:soundDuration, percentage:positionPercentage}));
        	if(positionPercentage == 100){
        		this.soundDictionary[soundName].positionTimer.stop();
        	}
        }
        
        /*
        
        Properties and Methods
        
        */		
                
		/**
		 * Sets the volume for a specific sound
		 * @param $tag : String - id of the sound for which to set the volume
		 * @param $volume : Number - volume amount
		 * 
		 */		
		public function setVolume($tag:String, $volume:Number):void
		{
			var transform:SoundTransform = this.soundDictionary[$tag].channel.soundTransform;
            transform.volume = $volume;
            this.soundDictionary[$tag].channel.soundTransform = transform;
		}
		
		/**
		 * Returns the volume of a specific sound
		 * @param $tag : String - id of the sound
		 * @return Number : volume
		 * 
		 */		
		public function getVolume($tag:String):Number
		{
			return this.soundDictionary[$tag].channel.soundTransform.volume;
		}
		
        /**
         * Returns the sound channel of the specified sound
         * @param $tag : String - id of the sound
         * @return : SoundChannel
         * 
         */		
        public function getChannel($tag:String):SoundChannel
        {
        	return this.soundDictionary[$tag].channel;
        }
        
        /**
         * Returns the required sound object
         * @param $tag : String - id of the sound
         * @return : Sound Object
         * 
         */        
        public function getSoundObject($tag:String):Sound
        {
        	return this.soundDictionary[$tag].sound;
        }
        
        /**
         * Returns the current position at which the sound is currently playing in seconds
         * @param $tag : String - id of the sound
         * @return : Number - position
         * 
         */        
        public function getPosition($tag:String):Number
        {
        	return Math.round(this.soundDictionary[$tag].channel.position * .001);
        }
        
        /**
         * Returns the current position at which the sound is currently playing in milliseconds
         * @param $tag : String - id of the sound
         * @return : Number - position
         * 
         */
        public function getPositionMS($tag:String):Number
        {
        	return this.soundDictionary[$tag].channel.position;
        }
        
        /**
         * Returns the duration of the sound in milliseconds
         * @param $tag : String - id of the sound
         * @return Number - duration in seconds
         * 
         */        
        public function getDuration($tag:String):Number
        {
        	return Math.round(this.soundDictionary[$tag].sound.length * .001);
        }
		
		/**
		 * Returns weather the sound is paused or not
		 * @param $tag : String - id of the sound
		 * @return 
		 * 
		 */		
		public function isPaused($tag:String):Boolean
		{
			return this.soundDictionary[$tag].paused;
		}
	}
}